/*
 * Copyright (c) 2010 Network Security Lab (NSL), Department of Electrical
 *                    Engineering, University of Washington
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Author: He Wu mdzz@uw.edu
 */

/**
 * @file network.c
 */

#include "network.h"
#include "util.h"
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/******************************************************************************/

// some global variables
pthread_mutex_t radioLock = PTHREAD_MUTEX_INITIALIZER;  //!< radio mutex

/******************************************************************************/

/**
 * @brief Send ACK packet.
 * @param destAddr Destination address.
 * @param localAddr Local address.
 * @return 0 if all is good.
 *
 * Called from within recv().
 */
static int sendACK(__u16 destAddr, __u16 localAddr);

/**
 * @brief Receives ACK packet.
 * @param destAddr Destination address.
 * @param localAddr Local address.
 * @return 0 if all is good.
 *
 * ACK packet has the same sequence number as the packet it is acknowledging.
 */
static int recvACK(__u16 destAddr, __u16 localAddr);

/**
 * @brief Sends out data packets.
 * @param destAddr Destination address.
 * @param localAddr Local address.
 * @param data Pointer to data to be sent.
 * @param dataSize Size of data to be sent.
 * @param port Packet type (using the "port" field in packet structure).
 * @param ACK_en Set to enable ACK.
 * @return 0 if all is good.
 */
static int send(__u16 destAddr, __u16 localAddr, __u8 *data,
                int dataSize, __u16 port, int ACK_en);

/**
 * @brief Receive packet.
 * @param localAddr Local address.
 * @param in_pkt Pointer to location to store incoming packet.
 * @return 0 if all is good.
 *
 * Receive function will receive any packet that is destined to the receiver or
 * a broadcast packet.
 */
static int recv(__u16 localAddr, packet* in_pkt);

/******************************************************************************/

int cc2420_init(int pwr_level, __u16 addr)
{
  int ret, fd_tosmac;

  // open as blocking mode
  fd_tosmac = open(TOSMAC_DEVICE, O_RDWR);
  if (fd_tosmac < 0) {
    fprintf(stderr, "Open error: %s\n", TOSMAC_DEVICE);
    return -1;
  }

  // set freq 2405 to 2480
  if (ioctl(fd_tosmac, TOSMAC_IOSETFREQ, 2405) < 0) {
    fprintf(stderr, "IOCTL set TOSMAC freq error: %s\n", TOSMAC_DEVICE);
    close(fd_tosmac);
    return -1;
  }

  // set channel 11 to 26
  if (ioctl(fd_tosmac, TOSMAC_IOSETCHAN, CHANNEL) < 0) {
    fprintf(stderr, "IOCTL set TOSMAC channel error: %s\n", TOSMAC_DEVICE);
    close(fd_tosmac);
    return -1;
  }

  // set local address
  if(ioctl(fd_tosmac, TOSMAC_IOSETADDR, addr) < 0)
  {
    fprintf (stderr, "IOCTL set TOSMAC address error: %s\n", TOSMAC_DEVICE);
    close(fd_tosmac);
    return -1;
  }

  /*
  // enable auto ack
  ret = ioctl(fd_tosmac, TOSMAC_IOAUTOACK);
  if(ret < 0)
  {
    fprintf (stderr, "IOCTL enable TOSMAC AutoAck error: %s\n", TOSMAC_DEVICE);
    close(fd_tosmac);
    return -1;
  }
  */

  // disable auto ack
  ret = ioctl(fd_tosmac, TOSMAC_IODISAUTOACK);
  if (ret < 0) {
    fprintf(stderr, "IOCTL enable TOSMAC AutoAck error: %s\n", TOSMAC_DEVICE);
    close(fd_tosmac);
    return -1;
  }

  //change max payload size (max value can be set is 116 bytes, change in "tosmacapi.h")
  if (ioctl(fd_tosmac, TOSMAC_IOSETMAXDATASIZE, MAX_PAYLOAD_SIZE) < 0) {
    fprintf(stderr, "IOCTL set TOSMAC max payload size error: %s\n",
        TOSMAC_DEVICE);
    close(fd_tosmac);
    return -1;
  }

  // set radio power level
  if (ioctl(fd_tosmac, TOSMAC_IOSETPOWER, pwr_level) < 0) {
    fprintf(stderr, "IOCTL set TOSMAC radio power level error: %s\n",
        TOSMAC_DEVICE);
    close(fd_tosmac);
    return -1;
  }

  // close device
  close(fd_tosmac);
  return 0;
}


void initPkt(packet *pkt, __u16 type)
{
  memset(pkt->data, 0x0, DATA_SIZE);  // set data payload to 0
  pkt->type = type;                   // set packet type
  pkt->senderAddr = get_addr();       // get local node address
  pkt->rss = MIN_RSSI;                // set RSSI to minimum value
  pkt->seq = 0;                       // set sequence number to 0
}


int h_send(__u16 destAddr, packet *out_pkt, int ACK_en)
{
  int ret;
  // send packet
  pthread_mutex_lock(&radioLock);     // lock radio
  if ((ret = send(destAddr, out_pkt->senderAddr, out_pkt->data, DATA_SIZE,
      out_pkt->type, ACK_en)) != 0) {
    pthread_mutex_unlock(&radioLock); // release radio
    printf("Packet failed to send to %x\n\n", destAddr);
    return -1;
  }
  pthread_mutex_unlock(&radioLock);   // release radio
  return 0;
}


int h_recv(__u16 localAddr, packet *in_pkt)
{
  int ret = 0;
  pthread_mutex_lock(&radioLock);     // lock radio
  if ((ret = recv(localAddr, in_pkt)) != 0) {
    pthread_mutex_unlock(&radioLock); // release radio
    return ret;
  }
  pthread_mutex_unlock(&radioLock);   // release radio
  return ret;
}

/******************************************************************************/

static int sendACK(__u16 destAddr, __u16 localAddr)
{
  TOS_Msg pkt;      // package
  int fd_tosmac;    // radio device

  // Open radio device, read in non-blocking mode, write in blocking mode
  fd_tosmac = open(TOSMAC_DEVICE, O_RDWR);

  // initialize sending packet
  memset(&pkt, 0, sizeof(pkt));

  // initialize sending packet
  memset(&pkt, 0, sizeof(pkt));
  pkt.length = MAX_PAYLOAD_SIZE;  // set data length of payload
  pkt.addr = destAddr;            // set destination address

  // NOTICE first 2 bytes of data section used to pass sender address
  // NOTICE bitwise operation used here, might be different on other platforms
  pkt.type = (__u8)localAddr;               // low bytes of address
  pkt.group = (__u8)(localAddr >> 8);       // high bytes of address
  pkt.data[0] = (__u8)((__u16)ACK_PKT_TYPE >> 8); // higher address
  pkt.data[1] = (__u8)ACK_PKT_TYPE;               // lower address

  // send out ACK
  if (write(fd_tosmac, &pkt, TOSH_DATA_LENGTH) >= 0) {
    printf("ACK sent...\n");
    close(fd_tosmac);
    return 0;
  }

  close(fd_tosmac);
  return -ACK_ERROR;
}


static int recvACK(__u16 destAddr, __u16 localAddr)
{
  int fd_tosmac;    // radio device
  TOS_Msg pkt;      // package
  fd_set read_fds;  // set of file descriptors for select()
  int fdmax;        // maximum file descriptor value
  int ret;          // return value from select()
  struct timeval tv;  // time vector for timeout
  __u16 ackAddr;    // address of the ACK sender
  __u16 type;       // type of received packet

  // initialize receiving packet
  memset(&pkt, 0, sizeof(pkt));

  // Open radio device, read in non-blocking mode, write in blocking mode
  fd_tosmac = open(TOSMAC_DEVICE, O_RDWR | O_NONBLOCK);

  // set max timeout
  // NOTICE total waiting time for recvACK will be ACK_TIMEOUT
  tv.tv_sec = ACK_TIMEOUT;
  tv.tv_usec = RECV_TIMEOUT_U;

  printf("Waiting for ACK...\n");

  // keep receiving packets until timeout
  while (1) {
    // check for remaining time
    if (tv.tv_sec == 0)
      break;

    // Watch fd_tosmac to see when it has input.
    FD_ZERO(&read_fds);
    FD_SET(fd_tosmac, &read_fds);

    // maximum file descriptor value
    fdmax = fd_tosmac;

    // block until packet arrives
    ret = select(fdmax + 1, &read_fds, NULL, NULL, &tv);

    // check for select return value
    if (ret == -1) {
      printf("Error occurred when waiting for ACK packet.\n");
      close(fd_tosmac); // close device
      return -ACK_ERROR;
    }

    // read packet when it arrives
    if (FD_ISSET(fd_tosmac, &read_fds)) {
      // read received packet
      read(fd_tosmac, &pkt, TOSH_DATA_LENGTH);

      // show packet information
      if (network_debug) {
        printf("Length:%d ", pkt.length);
        printf("fcfhi:%x ", pkt.fcfhi);
        printf("fcflo:%x ", pkt.fcflo);
        printf("Seq#:%x ", pkt.dsn);
        printf("DestPAN:%x ", pkt.destpan);
        printf("DestAddr:%x ", pkt.addr);
        printf("SrcAddr:%x%x ", pkt.group, pkt.type);
        //printf("TypeID:%x ", (__u8) pkt.type);
        //printf("GroupID:%x ", (__u8) pkt.group);
        printf("Strength:%d\n", (__s8)pkt.strength);
        printf("LQI:%d ", pkt.lqi);
        printf("CRC:%d\n\n", pkt.crc);
      }

      // NOTICE bitwise operation used here
      // record packet type (port number)
      type = (__u16)pkt.data[1] | ((__u16)pkt.data[0] << 8);

      // check packet type and if destination is current node
      if ((pkt.addr == localAddr) && (type == ACK_PKT_TYPE)) {
        // get destAddr
        ackAddr = (__u16)pkt.type | ((__u16)pkt.group << 8);
        // check if ACK is from the correct sender
        if (ackAddr == destAddr) { // if the ACK is from correct address
          printf("ACK received... from %x\n", ackAddr);
          close(fd_tosmac);
          return 0;
        }
        else {
          printf("ACK mismatch... with sender address #\n");
          continue;   // sender mismatch
        }
      }
      else {
        printf("ACK mismatch... with packet type or destination\n");
        continue;     // packet type mismatch
      }
    }
    // timeout
    else {
      printf("ACK timeout!\n");
      close(fd_tosmac);
      return -ACK_TIMEOUT_ERROR;
    }
  } // end while(1)

  close(fd_tosmac);
  return -ACK_ERROR;  // bad things have happened
}


static int send(__u16 destAddr, __u16 localAddr, __u8 *data, int dataSize,
                __u16 port, int ACK_en)
{
  int fd_tosmac;          // radio device
  int num_of_retries = 0; // # of retries
  TOS_Msg pkt;            // tosmac package

  // initialize sending packet
  memset(&pkt, 0, sizeof(pkt));
  pkt.length = MAX_PAYLOAD_SIZE;  // set data length of payload
  //pkt.fcfhi = 8;      // set FCF-hi to 0b00001000
  //pkt.fcflo = 0;      // set FCF-lo
  //pkt.dsn = 0;        // sequence # is assigned by radio automatically
  //pkt.destpan = 0;    // destination PAN is assigned by radio automatically
  pkt.addr = destAddr;  // set destination address

  // NOTICE first 2 bytes of data section used to pass sender address
  // NOTICE bitwise operation used here, might be different on other platforms
  pkt.type = (__u8)localAddr;         // low bytes of address
  pkt.group = (__u8)(localAddr >> 8); // high bytes of address
  pkt.data[0] = (__u8)(port >> 8);    // higher address
  pkt.data[1] = (__u8)port;           // lower address

  // fill packet with data
  memcpy(pkt.data + 2, data, dataSize);

  // send out packet
  while (1) {
    // Open radio device, blocking
    fd_tosmac = open(TOSMAC_DEVICE, O_RDWR);

    // send packet
    if (write(fd_tosmac, &pkt, TOSH_DATA_LENGTH) >= 0) {
      close(fd_tosmac);

      if (network_debug)
        printf("Packet being sent is: %s\n", (pkt.data + 2));

      // ACK
      if (ACK_en) {
        // receive ACK
        if (recvACK(destAddr, localAddr) == 0)
          break; // ACK received
        else {
          // check max retries
          if (num_of_retries++ >= RETRIES) {
            printf("Packet failed to send after retries.\n");
            close(fd_tosmac);
            return -SEND_ERROR;
          }
          printf("Resending current package...\n");
          continue; // resend packet
        }
      }
      else
        break; // skip ACK
    }
    else {
      printf("Packet failed to send.\n");
      close(fd_tosmac);
      return -RADIO_DRIVER_ERROR;
    }
  } // end while(1)

  return 0;
}


static int recv(__u16 localAddr, packet* in_pkt)
{
  int fd_tosmac;    // radio device
  int i;            // array index
  TOS_Msg pkt;      // package
  fd_set read_fds;  // set of file descriptors for select()
  int fdmax;        // maximum file descriptor value
  int ret;          // return value from select()
  struct timeval tv;  // time vector for timeout

  // initialize receiving packet
  memset(&pkt, 0, sizeof(pkt));

  // Open radio device, read in non-blocking mode, write in blocking mode
  fd_tosmac = open(TOSMAC_DEVICE, O_RDWR | O_NONBLOCK);

  // set max timeout
  // NOTICE total waiting time for recvACK will be ACK_TIMEOUT
  tv.tv_sec = RECV_TIMEOUT;
  tv.tv_usec = RECV_TIMEOUT_U;

  // keep receiving until gets packet or timeout
  while (1) {
    // check for remaining time
    if (tv.tv_sec == 0)
      break;

    // Watch fd_tosmac to see when it has input.
    FD_ZERO(&read_fds);
    FD_SET(fd_tosmac, &read_fds);

    // get maximum file descriptor value
    fdmax = fd_tosmac;

    // block until packet arrives
    ret = select(fdmax + 1, &read_fds, NULL, NULL, &tv);

    // check for select return val
    if (ret == -1) {
      close(fd_tosmac); // close device
      return -RECV_ERROR;
    }

    // read packet when it arrives
    if (FD_ISSET(fd_tosmac, &read_fds)) {
      // read received packet
      read(fd_tosmac, &pkt, TOSH_DATA_LENGTH);

      // show packet information
      if (network_debug) {
        printf("Length:%d ", pkt.length);
        printf("fcfhi:%x ", pkt.fcfhi);
        printf("fcflo:%x ", pkt.fcflo);
        printf("Seq#:%x ", pkt.dsn);
        printf("DestPAN:%x ", pkt.destpan);
        printf("DestAddr:%x ", pkt.addr);
        printf("SrcAddr:%x%x ", pkt.group, pkt.type);
        //printf("TypeID:%x ", (__u8) pkt.type);
        //printf("GroupID:%x ", (__u8) pkt.group);
        printf("Strength:%d\n", (__s8)pkt.strength);
        printf("LQI:%d ", pkt.lqi);
        printf("CRC:%d\n\n", pkt.crc);
      }

      // check packet dest
      if ((pkt.addr == localAddr) || (pkt.addr == BROADCAST_ADDR)) {
        printf("Packet received!\n");

        // get data from packet
        memcpy(in_pkt->data, pkt.data + 2, DATA_SIZE);

        // get RSSI, converting unsigned to signed!
        in_pkt->rss = (__s8)pkt.strength;
        // get sequence number
        in_pkt->seq = pkt.dsn;

        // NOTICE bitwise operation used here
        // record packet type (port number)
        in_pkt->type = (__u16)pkt.data[1] | ((__u16)pkt.data[0] << 8);
        // get destAddr
        in_pkt->senderAddr = (__u16)pkt.type | ((__u16)pkt.group << 8);

        close(fd_tosmac); // close device

        // send a ACK
        // ACK not needed for routing packets
        if ((in_pkt->type == CMD_PKT_TYPE)
            || (in_pkt->type == TO_BASE_DATA_PKT_TYPE)
            || (in_pkt->type == HEALTH_DATA_TYPE)) {
          // send multiple ACKs if needed
          for (i = 0; i < ACK_RESEND; i++) {
            if (sendACK(in_pkt->senderAddr, localAddr) < 0) {
              printf("Failed to send ACK.\n\n");
              return -ACK_ERROR;
            }
          }
        }
        return 0;     // ACK sent
      }
      else continue;  // destination mismatch
    }
    else {
      printf("Receive timeout!\n");
      close(fd_tosmac); // close device
      return -RECV_TIMEOUT_ERROR;
    }
  }

  close(fd_tosmac); // close device
  return -RECV_ERROR;
}
